#!/usr/bin/env python
#coding=utf-8
import datetime
import hashlib
from random import sample
import logging

from google.appengine.ext import db
from google.appengine.ext.db.polymodel import PolyModel
from google.appengine.api import users
from google.appengine.api import memcache
#from StringIO import StringIO
#log = StringIO()

########################
# helpers
# TODO: maybe it should be decremented after deletion.
class Counter(db.Model):
    count = db.IntegerProperty(default=1)

def set_memcache(key, value, time=600):
    if not memcache.set(key, value, time):
        logging.error("Memcache set failed for %s." % key)

########################

# new models under construction

# class Content(PolyModel):
#     created = db.DateTimeProperty(auto_now_add=True)
#     modified = db.DateTimeProperty(auto_now=True)
#     title = db.StringProperty(multiline=False, default='')
#     slug = db.StringProperty(multiline=False, default='')
#     text = db.TextProperty(default='')


# class Post(Content):
#     tags = db.StringListProperty()


# class Page(Content):
#     order = db.IntegerProperty(default=1)

# new models end

class BlogConfig(db.Model):
    title = db.StringProperty(multiline=False)
    subtitle = db.StringProperty(multiline=False)
    entries_per_page= db.IntegerProperty(default=10)
    theme_name = db.StringProperty(multiline=False)
    timedelta=db.FloatProperty()# hours
    language=db.StringProperty()
    version = db.FloatProperty(default=0.0)

    def edit(self, **kwargs):
        self.title = kwargs.pop('title', self.title)
        self.subtitle = kwargs.pop('subtitle', self.subtitle)
        self.entries_per_page = kwargs.pop('entries_per_page', self.entries_per_page)
        self.theme_name = kwargs.pop('theme_name', self.theme_name)
        self.timedelta = kwargs.pop('timedelta', self.timedelta)
        self.language = kwargs.pop('language', self.language)
        self.version = kwargs.get('version', self.version)
        self.put()
        set_memcache("config", self)
        return self


def get_blog_config():
    config = memcache.get("config")

    if config is None:
        config = BlogConfig.get_or_insert("the_one",
                            title='Tublog',
                            subtitle='Yet Another GAE Blog Engine.',
                            entries_per_page=10,
                            theme_name='k2',
                            timedelta=8.0,
                            language='en-us')
        config.put()
        set_memcache("config", config)
    return config

class Tag(db.Model):
    name = db.StringProperty(multiline=False)
    count = db.IntegerProperty(default=0)

    @classmethod
    def add(cls, value):
        if value and value.strip():
            value = value.strip()
            tag = Tag.get_by_key_name(value)
            if not tag:
                tag = Tag(key_name=value)
                tag.name = value

            tag.count += 1
            tag.put()
            return tag
        else:
            return None

    @classmethod
    def remove(cls, value):
        if value and value.strip():
            value = value.strip()
            tag= Tag.get_by_key_name(value)
            if tag:
                if tag.count > 1:
                    tag.count-=1
                    tag.put()
                else:
                    tag.delete()


class ContentBase(db.Model):
    index = db.IntegerProperty(required=True)
    date = db.DateTimeProperty(auto_now=True)

    def datestr(self):
        timedelta = memcache.get("timedelta")
        if timedelta is None:
            timedelta = datetime.timedelta(seconds=3600*get_blog_config().timedelta)
            set_memcache('timedelta', timedelta)
        return (self.date + timedelta).ctime()

    @classmethod
    def new_(cls, **kwargs): # bad method name here!!
        basename = cls.__name__        # TODO: clean up "basename" (duplication is everywhere)

        def txn():
            counter = Counter.get_by_key_name(basename)
            if counter is None:
                counter = Counter(key_name=basename)
            new_index = counter.count
            counter.count += 1
            counter.put()

            key_name = basename + str(new_index)
            new_item = cls(
                key_name=key_name,
                parent=counter,
                index=new_index,
                **kwargs)
            new_item.put()
            #set_memcache(key_name, new_item)
            return new_item

        return db.run_in_transaction(txn)

    @classmethod
    def get_by_index(cls, index):
        basename = cls.__name__
        key_name = basename + str(index)
        item = memcache.get(key_name)
        if item is None:
            item = cls.get_by_key_name(key_name,
                                       parent=db.Key.from_path('Counter', basename))
        set_memcache(key_name, item)
        return item

    @classmethod
    def get_items(cls, start_index=None, num=5, bidirection=False, custom_gql={}):
        prev = None
        next = None
        if start_index is None:
            items = cls.gql(custom_gql.get('no_index', 'ORDER BY index DESC')).fetch(num + 1)
        else:
            start_index = int(start_index)
            items = cls.gql(custom_gql.get('with_index', 'WHERE index <= %s ORDER BY index DESC' % start_index)).fetch(num + 1)
            next = cls.gql(custom_gql.get('next', 'WHERE index > %s ORDER BY index' % start_index)).fetch(num)
            if next:
                next = next[-1]
        if len(items) > num:
            prev = items[-1]
            items = items[:num]
        if bidirection:
            return items, prev, next
        return items, prev

    @classmethod
    def get_count(cls):
        # since the counter is never decremented (does it need to be?)
        # so this count will be "a little" bigger...
        basename = cls.__name__
        counter = memcache.get(basename)
        if counter is None:
            counter = Counter.get_by_key_name(basename)
            if counter is None:
                return 0
            set_memcache(basename, counter)
        return counter.count

def _make_gql(tag, start_index):
    # dirty string concat...
    # any danger that common SQL-injections will happen?
    header = "WHERE entype = 'post'"
    if tag:
        header += "AND tags = '%s'" % tag
    next = header
    if start_index:
        header += "AND index <= %s" % start_index
        next += "AND index > %s ORDER BY index" % start_index
    header += "ORDER BY index DESC"
    return header, next

class Entry(ContentBase):
    author = db.UserProperty(auto_current_user_add=True)
    content = db.TextProperty(default='')
    title = db.StringProperty(multiline=False)
    tags = db.StringListProperty()
    entype = db.StringProperty(default='post', choices=['post', 'page'])

    def set_tags(self, values):
        if not values:return

        if type(values) == type([]):
            tags = set(values)
        else:
            tags = set(values.split(','))

        if not self.tags:
            removelist = []
            addlist = tags
        else:
            # search different tags
            # TODO: using builtin set will be better.
            removelist=[n for n in self.tags if n not in tags]
            addlist=[n for n in tags if n not in self.tags]
        for v in removelist:
            Tag.remove(v)
        for v in addlist:
            Tag.add(v)
        self.tags = list(tags)

    @classmethod
    def new(cls, **kwargs):
        tags = kwargs.pop('tags', [])

        e = cls.new_(**kwargs)
        e.set_tags(tags)
        e.put()
        return e

    def edit(self, **kwargs):
        self.title = kwargs.pop('title', self.title)
        self.content = kwargs.pop('content', self.content)
        self.entype = kwargs.pop('entype', self.entype)
        self.set_tags(kwargs.pop('tags', self.tags))
        self.put()
        basename = self.__class__.__name__
        key_name = basename + str(self.index)
        set_memcache(key_name, self)
        return self

    @classmethod
    def del_by_index(cls, index):
        e = cls.get_by_index(index)
        if e:
            # for c in e.comments:
            #     c.delete()
            basename = cls.__name__
            key_name = basename + str(index)
            memcache.delete(key_name)
            db.delete(e.comments)
            for t in e.tags:
                Tag.remove(t)
            e.delete()
            return True
        return False

    @classmethod
    def get_posts(cls, start_index=None, num=5, bidirection=False, tag=''):
        no_index, discard = _make_gql(tag, None)
        with_index, next = _make_gql(tag, start_index)
        return cls.get_items(start_index, num, bidirection,
                      custom_gql={'no_index': no_index,
                                  'with_index': with_index,
                                  'next': next})


def get_pages(limit=0):
    headers = db.GqlQuery("SELECT * FROM Entry WHERE entype = 'page' ORDER BY index DESC")
    if limit > 0:
        return headers.fetch(limit)
    return headers

class Comment(ContentBase):
    entry = db.ReferenceProperty(Entry, collection_name='comments')
    content = db.TextProperty()
    author = db.StringProperty()
    email = db.StringProperty()
    site = db.StringProperty()

    def shortcontent(self,len=20):
        key_name = 'sc_%s' % self.key().name()
        sc = memcache.get(key_name)
        if sc is None:
            sc = self.content[:len]
            set_memcache(key_name, sc)
        return sc

    @classmethod
    def del_by_index(cls, index):
        c = cls.get_by_index(index)
        if c:
            basename = cls.__name__
            key_name = basename + str(index)
            memcache.delete(key_name)
            c.delete()

    @classmethod
    def new(cls, **kwargs):
        c = cls.new_(**kwargs)
        return c


#originated from http://code.google.com/p/pyweblog
class RpcUser(db.Model):
    passwd = db.StringProperty()
    passwdstr = db.StringProperty()
    real_user = db.UserProperty(required = True)

    @classmethod
    def regenerate_password(cls, user):
        """
        Generate a random string for using as api password, api user is user's full email
        """

        s = 'abcdefghijklmnopqrstuvwxyz1234567890'
        password = ''.join(sample(s, 8))
        admin = cls.get_by_key_name(user.email())
        if not admin:
            admin = cls(real_user = user, key_name=user.email())
        admin.passwd = hashlib.md5(user.email() + 'tublog' + password).hexdigest()
        admin.passwdstr = password
        admin.put()
        return password


    @classmethod
    def get_passwd(cls, user):
        admin = cls.get_by_key_name(user.email())
        if admin:
            return admin.passwdstr
        else:
            return cls.regenerate_password(user)


    @classmethod
    def check_api_user_pass(cls, email, password):
        user = users.User(email)
        admin = cls.all().filter('real_user = ', user).get()

        if not admin:
            return None

        if admin.passwd == hashlib.md5(user.email() + 'tublog' + password).hexdigest():
            return user
        return None


class Widget(db.Model):
    fullname = db.StringProperty(required=True)
    data = db.TextProperty(default='')
    enabled = db.BooleanProperty()
    order = db.IntegerProperty(default=1)
